/* -------------------------------------------------------------------------
 *
 * seclabel.c
 *	  支持安全标签功能的例程。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * -------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/genam.h"
#include "access/htup_details.h"
#include "access/relation.h"
#include "access/table.h"
#include "catalog/catalog.h"
#include "catalog/indexing.h"
#include "catalog/pg_seclabel.h"
#include "catalog/pg_shseclabel.h"
#include "commands/seclabel.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/memutils.h"
#include "utils/rel.h"

typedef struct
{
	const char *provider_name;
	check_object_relabel_type hook;
} LabelProvider;

static List *label_provider_list = NIL;

static bool fc_SecLabelSupportsObjectType(ObjectType fc_objtype)
{
	switch (fc_objtype)
	{
		case OBJECT_AGGREGATE:
		case OBJECT_COLUMN:
		case OBJECT_DATABASE:
		case OBJECT_DOMAIN:
		case OBJECT_EVENT_TRIGGER:
		case OBJECT_FOREIGN_TABLE:
		case OBJECT_FUNCTION:
		case OBJECT_LANGUAGE:
		case OBJECT_LARGEOBJECT:
		case OBJECT_MATVIEW:
		case OBJECT_PROCEDURE:
		case OBJECT_PUBLICATION:
		case OBJECT_ROLE:
		case OBJECT_ROUTINE:
		case OBJECT_SCHEMA:
		case OBJECT_SEQUENCE:
		case OBJECT_SUBSCRIPTION:
		case OBJECT_TABLE:
		case OBJECT_TABLESPACE:
		case OBJECT_TYPE:
		case OBJECT_VIEW:
			return true;

		case OBJECT_ACCESS_METHOD:
		case OBJECT_AMOP:
		case OBJECT_AMPROC:
		case OBJECT_ATTRIBUTE:
		case OBJECT_CAST:
		case OBJECT_COLLATION:
		case OBJECT_CONVERSION:
		case OBJECT_DEFAULT:
		case OBJECT_DEFACL:
		case OBJECT_DOMCONSTRAINT:
		case OBJECT_EXTENSION:
		case OBJECT_FDW:
		case OBJECT_FOREIGN_SERVER:
		case OBJECT_INDEX:
		case OBJECT_OPCLASS:
		case OBJECT_OPERATOR:
		case OBJECT_OPFAMILY:
		case OBJECT_PARAMETER_ACL:
		case OBJECT_POLICY:
		case OBJECT_PUBLICATION_NAMESPACE:
		case OBJECT_PUBLICATION_REL:
		case OBJECT_RULE:
		case OBJECT_STATISTIC_EXT:
		case OBJECT_TABCONSTRAINT:
		case OBJECT_TRANSFORM:
		case OBJECT_TRIGGER:
		case OBJECT_TSCONFIGURATION:
		case OBJECT_TSDICTIONARY:
		case OBJECT_TSPARSER:
		case OBJECT_TSTEMPLATE:
		case OBJECT_USER_MAPPING:
			return false;

			/*
			 * 这里故意没有 default: 情况；我们希望
			 * 编译器在上面没有处理新的 ObjectType 时发出警告。
			 */
	}

	/* 不应该到这里，但如果到了，就说“没有支持” */
	return false;
}

/*
 * ExecSecLabelStmt --
 *
 * 将安全标签应用于数据库对象。
 *
 * 返回应用策略的对象的 ObjectAddress。
 */
ObjectAddress ExecSecLabelStmt(SecLabelStmt *fc_stmt)
{
	LabelProvider *fc_provider = NULL;
	ObjectAddress fc_address;
	Relation	fc_relation;
	ListCell   *fc_lc;

	/*
	 * 查找命名的标签提供者，如果没有指定，请检查是否正好有一个，如果有，则使用它。
	 */
	if (fc_stmt->provider == NULL)
	{
		if (label_provider_list == NIL)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("no security label providers have been loaded")));
		if (list_length(label_provider_list) != 1)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("must specify provider when multiple security label providers have been loaded")));
		fc_provider = (LabelProvider *) linitial(label_provider_list);
	}
	else
	{
		foreach(fc_lc, label_provider_list)
		{
			LabelProvider *fc_lp = lfirst(fc_lc);

			if (strcmp(fc_stmt->provider, fc_lp->provider_name) == 0)
			{
				fc_provider = fc_lp;
				break;
			}
		}
		if (fc_provider == NULL)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("security label provider \"%s\" is not loaded",
							fc_stmt->provider)));
	}

	if (!fc_SecLabelSupportsObjectType(fc_stmt->objtype))
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("security labels are not supported for this type of object")));

	/*
	 * 将标识该对象的解析器表示转换为 ObjectAddress。
	 * get_object_address() 如果对象不存在将抛出错误，并且还会对目标获取锁，
	 * 以防止并发修改。
	 */
	fc_address = get_object_address(fc_stmt->objtype, fc_stmt->object,
								 &fc_relation, ShareUpdateExclusiveLock, false);

	/* 需要目标对象的所有权。 */
	check_object_ownership(GetUserId(), fc_stmt->objtype, fc_address,
						   fc_stmt->object, fc_relation);

	/* 根据需要执行其他完整性检查。 */
	switch (fc_stmt->objtype)
	{
		case OBJECT_COLUMN:

			/*
			 * 仅允许在表、视图、物化视图、复合类型和外部表的列上使用安全标签（
			 * 这些是 pg_dump 将转储标签的唯一 relkinds）。
			 */
			if (fc_relation->rd_rel->relkind != RELKIND_RELATION &&
				fc_relation->rd_rel->relkind != RELKIND_VIEW &&
				fc_relation->rd_rel->relkind != RELKIND_MATVIEW &&
				fc_relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
				fc_relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
				fc_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
				ereport(ERROR,
						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
						 errmsg("cannot set security label on relation \"%s\"",
								RelationGetRelationName(fc_relation)),
						 errdetail_relkind_not_supported(fc_relation->rd_rel->relkind)));
			break;
		default:
			break;
	}

	/* 提供者在这里获取控制，可能会抛出 ERROR 以否决新标签。 */
	fc_provider->hook(&fc_address, fc_stmt->label);

	/* 应用新标签。 */
	SetSecurityLabel(&fc_address, fc_provider->provider_name, fc_stmt->label);

	/*
	 * 如果 get_object_address() 为我们打开了关系，
	 * 我们会关闭它以保持引用计数的正确性
	 * - 但我们会在提交时保留 get_object_address() 获取的任何锁，
	 * 以防止并发活动。
	 */
	if (fc_relation != NULL)
		relation_close(fc_relation, NoLock);

	return fc_address;
}

/*
 * GetSharedSecurityLabel 返回指定提供者的共享对象的安全标签，
 * 如果没有此类标签，则返回 NULL。
 */
static char * fc_GetSharedSecurityLabel(const ObjectAddress *fc_object, const char *fc_provider)
{
	Relation	fc_pg_shseclabel;
	ScanKeyData fc_keys[3];
	SysScanDesc fc_scan;
	HeapTuple	fc_tuple;
	Datum		fc_datum;
	bool		fc_isnull;
	char	   *fc_seclabel = NULL;

	ScanKeyInit(&fc_keys[0],
				Anum_pg_shseclabel_objoid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_object->objectId));
	ScanKeyInit(&fc_keys[1],
				Anum_pg_shseclabel_classoid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_object->classId));
	ScanKeyInit(&fc_keys[2],
				Anum_pg_shseclabel_provider,
				BTEqualStrategyNumber, F_TEXTEQ,
				CStringGetTextDatum(fc_provider));

	fc_pg_shseclabel = table_open(SharedSecLabelRelationId, AccessShareLock);

	fc_scan = systable_beginscan(fc_pg_shseclabel, SharedSecLabelObjectIndexId,
							  criticalSharedRelcachesBuilt, NULL, 3, fc_keys);

	fc_tuple = systable_getnext(fc_scan);
	if (HeapTupleIsValid(fc_tuple))
	{
		fc_datum = heap_getattr(fc_tuple, Anum_pg_shseclabel_label,
							 RelationGetDescr(fc_pg_shseclabel), &fc_isnull);
		if (!fc_isnull)
			fc_seclabel = TextDatumGetCString(fc_datum);
	}
	systable_endscan(fc_scan);

	table_close(fc_pg_shseclabel, AccessShareLock);

	return fc_seclabel;
}

/*
 * GetSecurityLabel 返回指定提供者的共享或数据库对象的安全标签，
 * 如果没有此类标签，则返回 NULL。
 */
char * GetSecurityLabel(const ObjectAddress *fc_object, const char *fc_provider)
{
	Relation	fc_pg_seclabel;
	ScanKeyData fc_keys[4];
	SysScanDesc fc_scan;
	HeapTuple	fc_tuple;
	Datum		fc_datum;
	bool		fc_isnull;
	char	   *fc_seclabel = NULL;

	/* 共享对象有自己的安全标签目录。 */
	if (IsSharedRelation(fc_object->classId))
		return fc_GetSharedSecurityLabel(fc_object, fc_provider);

	/* 必须是未共享对象，因此检查 pg_seclabel。 */
	ScanKeyInit(&fc_keys[0],
				Anum_pg_seclabel_objoid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_object->objectId));
	ScanKeyInit(&fc_keys[1],
				Anum_pg_seclabel_classoid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_object->classId));
	ScanKeyInit(&fc_keys[2],
				Anum_pg_seclabel_objsubid,
				BTEqualStrategyNumber, F_INT4EQ,
				Int32GetDatum(fc_object->objectSubId));
	ScanKeyInit(&fc_keys[3],
				Anum_pg_seclabel_provider,
				BTEqualStrategyNumber, F_TEXTEQ,
				CStringGetTextDatum(fc_provider));

	fc_pg_seclabel = table_open(SecLabelRelationId, AccessShareLock);

	fc_scan = systable_beginscan(fc_pg_seclabel, SecLabelObjectIndexId, true,
							  NULL, 4, fc_keys);

	fc_tuple = systable_getnext(fc_scan);
	if (HeapTupleIsValid(fc_tuple))
	{
		fc_datum = heap_getattr(fc_tuple, Anum_pg_seclabel_label,
							 RelationGetDescr(fc_pg_seclabel), &fc_isnull);
		if (!fc_isnull)
			fc_seclabel = TextDatumGetCString(fc_datum);
	}
	systable_endscan(fc_scan);

	table_close(fc_pg_seclabel, AccessShareLock);

	return fc_seclabel;
}

/*
 * SetSharedSecurityLabel 是 SetSecurityLabel 的辅助函数，
 * 用于处理共享数据库对象。
 */
static void fc_SetSharedSecurityLabel(const ObjectAddress *fc_object,
					   const char *fc_provider, const char *fc_label)
{
	Relation	fc_pg_shseclabel;
	ScanKeyData fc_keys[4];
	SysScanDesc fc_scan;
	HeapTuple	fc_oldtup;
	HeapTuple	fc_newtup = NULL;
	Datum		fc_values[Natts_pg_shseclabel];
	bool		fc_nulls[Natts_pg_shseclabel];
	bool		fc_replaces[Natts_pg_shseclabel];

	/* 如果需要，准备形成或更新元组。 */
	memset(fc_nulls, false, sizeof(fc_nulls));
	memset(fc_replaces, false, sizeof(fc_replaces));
	fc_values[Anum_pg_shseclabel_objoid - 1] = ObjectIdGetDatum(fc_object->objectId);
	fc_values[Anum_pg_shseclabel_classoid - 1] = ObjectIdGetDatum(fc_object->classId);
	fc_values[Anum_pg_shseclabel_provider - 1] = CStringGetTextDatum(fc_provider);
	if (fc_label != NULL)
		fc_values[Anum_pg_shseclabel_label - 1] = CStringGetTextDatum(fc_label);

	/* 使用索引搜索匹配的旧元组 */
	ScanKeyInit(&fc_keys[0],
				Anum_pg_shseclabel_objoid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_object->objectId));
	ScanKeyInit(&fc_keys[1],
				Anum_pg_shseclabel_classoid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_object->classId));
	ScanKeyInit(&fc_keys[2],
				Anum_pg_shseclabel_provider,
				BTEqualStrategyNumber, F_TEXTEQ,
				CStringGetTextDatum(fc_provider));

	fc_pg_shseclabel = table_open(SharedSecLabelRelationId, RowExclusiveLock);

	fc_scan = systable_beginscan(fc_pg_shseclabel, SharedSecLabelObjectIndexId, true,
							  NULL, 3, fc_keys);

	fc_oldtup = systable_getnext(fc_scan);
	if (HeapTupleIsValid(fc_oldtup))
	{
		if (fc_label == NULL)
			CatalogTupleDelete(fc_pg_shseclabel, &fc_oldtup->t_self);
		else
		{
			fc_replaces[Anum_pg_shseclabel_label - 1] = true;
			fc_newtup = heap_modify_tuple(fc_oldtup, RelationGetDescr(fc_pg_shseclabel),
									   fc_values, fc_nulls, fc_replaces);
			CatalogTupleUpdate(fc_pg_shseclabel, &fc_oldtup->t_self, fc_newtup);
		}
	}
	systable_endscan(fc_scan);

	/* 如果未找到旧元组，则插入新的 */
	if (fc_newtup == NULL && fc_label != NULL)
	{
		fc_newtup = heap_form_tuple(RelationGetDescr(fc_pg_shseclabel),
								 fc_values, fc_nulls);
		CatalogTupleInsert(fc_pg_shseclabel, fc_newtup);
	}

	if (fc_newtup != NULL)
		heap_freetuple(fc_newtup);

	table_close(fc_pg_shseclabel, RowExclusiveLock);
}

/*
 * SetSecurityLabel 尝试将指定提供者在指定对象上的安全标签设置为给定值。
 * NULL 表示应删除任何现有标签。
 */
void SetSecurityLabel(const ObjectAddress *fc_object,
				 const char *fc_provider, const char *fc_label)
{
	Relation	fc_pg_seclabel;
	ScanKeyData fc_keys[4];
	SysScanDesc fc_scan;
	HeapTuple	fc_oldtup;
	HeapTuple	fc_newtup = NULL;
	Datum		fc_values[Natts_pg_seclabel];
	bool		fc_nulls[Natts_pg_seclabel];
	bool		fc_replaces[Natts_pg_seclabel];

	/* 共享对象有自己的安全标签目录。 */
	if (IsSharedRelation(fc_object->classId))
	{
		fc_SetSharedSecurityLabel(fc_object, fc_provider, fc_label);
		return;
	}

	/* 如果需要，准备形成或更新元组。 */
	memset(fc_nulls, false, sizeof(fc_nulls));
	memset(fc_replaces, false, sizeof(fc_replaces));
	fc_values[Anum_pg_seclabel_objoid - 1] = ObjectIdGetDatum(fc_object->objectId);
	fc_values[Anum_pg_seclabel_classoid - 1] = ObjectIdGetDatum(fc_object->classId);
	fc_values[Anum_pg_seclabel_objsubid - 1] = Int32GetDatum(fc_object->objectSubId);
	fc_values[Anum_pg_seclabel_provider - 1] = CStringGetTextDatum(fc_provider);
	if (fc_label != NULL)
		fc_values[Anum_pg_seclabel_label - 1] = CStringGetTextDatum(fc_label);

	/* 使用索引搜索匹配的旧元组 */
	ScanKeyInit(&fc_keys[0],
				Anum_pg_seclabel_objoid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_object->objectId));
	ScanKeyInit(&fc_keys[1],
				Anum_pg_seclabel_classoid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_object->classId));
	ScanKeyInit(&fc_keys[2],
				Anum_pg_seclabel_objsubid,
				BTEqualStrategyNumber, F_INT4EQ,
				Int32GetDatum(fc_object->objectSubId));
	ScanKeyInit(&fc_keys[3],
				Anum_pg_seclabel_provider,
				BTEqualStrategyNumber, F_TEXTEQ,
				CStringGetTextDatum(fc_provider));

	fc_pg_seclabel = table_open(SecLabelRelationId, RowExclusiveLock);

	fc_scan = systable_beginscan(fc_pg_seclabel, SecLabelObjectIndexId, true,
							  NULL, 4, fc_keys);

	fc_oldtup = systable_getnext(fc_scan);
	if (HeapTupleIsValid(fc_oldtup))
	{
		if (fc_label == NULL)
			CatalogTupleDelete(fc_pg_seclabel, &fc_oldtup->t_self);
		else
		{
			fc_replaces[Anum_pg_seclabel_label - 1] = true;
			fc_newtup = heap_modify_tuple(fc_oldtup, RelationGetDescr(fc_pg_seclabel),
									   fc_values, fc_nulls, fc_replaces);
			CatalogTupleUpdate(fc_pg_seclabel, &fc_oldtup->t_self, fc_newtup);
		}
	}
	systable_endscan(fc_scan);

	/* 如果未找到旧元组，则插入新的 */
	if (fc_newtup == NULL && fc_label != NULL)
	{
		fc_newtup = heap_form_tuple(RelationGetDescr(fc_pg_seclabel),
								 fc_values, fc_nulls);
		CatalogTupleInsert(fc_pg_seclabel, fc_newtup);
	}

	/* 根据需要更新索引 */
	if (fc_newtup != NULL)
		heap_freetuple(fc_newtup);

	table_close(fc_pg_seclabel, RowExclusiveLock);
}

/*
 * DeleteSharedSecurityLabel 是 DeleteSecurityLabel 的辅助函数，
 * 用于处理共享数据库对象。
 */
void DeleteSharedSecurityLabel(Oid fc_objectId, Oid fc_classId)
{
	Relation	fc_pg_shseclabel;
	ScanKeyData fc_skey[2];
	SysScanDesc fc_scan;
	HeapTuple	fc_oldtup;

	ScanKeyInit(&fc_skey[0],
				Anum_pg_shseclabel_objoid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_objectId));
	ScanKeyInit(&fc_skey[1],
				Anum_pg_shseclabel_classoid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_classId));

	fc_pg_shseclabel = table_open(SharedSecLabelRelationId, RowExclusiveLock);

	fc_scan = systable_beginscan(fc_pg_shseclabel, SharedSecLabelObjectIndexId, true,
							  NULL, 2, fc_skey);
	while (HeapTupleIsValid(fc_oldtup = systable_getnext(fc_scan)))
		CatalogTupleDelete(fc_pg_shseclabel, &fc_oldtup->t_self);
	systable_endscan(fc_scan);

	table_close(fc_pg_shseclabel, RowExclusiveLock);
}

/*
 * DeleteSecurityLabel 删除对象（以及任何子对象，如果适用）的所有安全标签。
 */
void DeleteSecurityLabel(const ObjectAddress *fc_object)
{
	Relation	fc_pg_seclabel;
	ScanKeyData fc_skey[3];
	SysScanDesc fc_scan;
	HeapTuple	fc_oldtup;
	int			fc_nkeys;

	/* 共享对象有自己的安全标签目录。 */
	if (IsSharedRelation(fc_object->classId))
	{
		Assert(fc_object->objectSubId == 0);
		DeleteSharedSecurityLabel(fc_object->objectId, fc_object->classId);
		return;
	}

	ScanKeyInit(&fc_skey[0],
				Anum_pg_seclabel_objoid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_object->objectId));
	ScanKeyInit(&fc_skey[1],
				Anum_pg_seclabel_classoid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_object->classId));
	if (fc_object->objectSubId != 0)
	{
		ScanKeyInit(&fc_skey[2],
					Anum_pg_seclabel_objsubid,
					BTEqualStrategyNumber, F_INT4EQ,
					Int32GetDatum(fc_object->objectSubId));
		fc_nkeys = 3;
	}
	else
		fc_nkeys = 2;

	fc_pg_seclabel = table_open(SecLabelRelationId, RowExclusiveLock);

	fc_scan = systable_beginscan(fc_pg_seclabel, SecLabelObjectIndexId, true,
							  NULL, fc_nkeys, fc_skey);
	while (HeapTupleIsValid(fc_oldtup = systable_getnext(fc_scan)))
		CatalogTupleDelete(fc_pg_seclabel, &fc_oldtup->t_self);
	systable_endscan(fc_scan);

	table_close(fc_pg_seclabel, RowExclusiveLock);
}

void register_label_provider(const char *fc_provider_name, check_object_relabel_type fc_hook)
{
	LabelProvider *fc_provider;
	MemoryContext fc_oldcxt;

	fc_oldcxt = MemoryContextSwitchTo(TopMemoryContext);
	fc_provider = palloc(sizeof(LabelProvider));
	fc_provider->provider_name = pstrdup(fc_provider_name);
	fc_provider->hook = fc_hook;
	label_provider_list = lappend(label_provider_list, fc_provider);
	MemoryContextSwitchTo(fc_oldcxt);
}
